# gozerbot/botbase.py
#
#

""" bot base class. provides data/methods common to all bots. """

# IMPORT SECTION

# gozerbot imports
from threads.thr import start_new_thread
from utils.log import rlog
from less import Less
from persist.pdol import Pdol
from utils.dol import Dol
from utils.lazydict import LazyDict
from persist.persiststate import PersistState
from channels import Channels
from datadir import datadir
from persist.pdod import Pdod
from config import config, Config, fleetbotconfigtxt
from runner import runners_start
from monitor import Monitor
from callbacks import callbacks, gn_callbacks
from cache import userhosts
from wait import Wait, Privwait
from eventhandler import mainhandler
from exit import globalshutdown
from utils.exception import handle_exception
from plugins import plugins

# throttle support
from gozerbot.plugs.throttle import state as throttlestate

# basic imports
import time, threading, os, types, sys, copy

# END IMPORT

cpy = copy.deepcopy

class BotBase(object):

    """ 

            Base class for all bots. Inherit from this. 

            :param cfg: configuration 
            :type cfg: dict like

    """

    def __init__(self, name, cfg={}):
        self.name = name
        self.encoding = sys.getdefaultencoding()

        # if cfg is not passed on create one
        if not cfg:
            cfg = Config(inittxt=fleetbotconfigtxt)

        if not cfg.has_key('dir'):
           cfg['dir'] = os.getcwd()

        if not cfg.has_key('user'):
           cfg['user'] = 'gozerbot'

        if not cfg.has_key('type'):
           cfg['type'] = 'gozernet'

        # set attributes based on config 
        self.__dict__.update(cfg)

        # if name not set in config file use the directory name

        if not cfg.has_key('name'):
            if 'fleet' in cfg['dir']:
                self.name = cfg.dir.split(os.sep)[-1]
            else:
                self.name = name

        # default nick to gozerbot
        if not cfg.has_key('nick'):
            self.nick = 'gozerbot'

        if not cfg.has_key('server'):
            self.server = 'server not set'

        try:
            self.host = cfg['host']
            if not self.host:
                self.host = self.user.split('@')[1]
        except (KeyError, IndexError):
            try:
                self.host = self.user.split('@')[1]
            except (ValueError, IndexError):
                self.host = 'host not set'


        # default port to 0 (use default port)
        if not cfg.has_key('port'):
            self.port  = 0

        if not cfg.has_key('ipv6'):
            self.ipv6  = 0
        else:
            self.ipv6 = cfg['ipv6']
        
        # make sure bot name is not a directory
        if '..' in self.name or '/' in self.name:
            raise Exception('wrong bot name %s' % self.name)

        # set datadir to datadir/fleet/<botname>
        self.datadir = datadir + os.sep + 'fleet' + os.sep + self.name

        # set datadir to datadir/fleet/<botname>
        if hasattr(os, 'mkdir'):
            if not os.path.exists(self.datadir):
                os.mkdir(self.datadir)

        # bot state
        self.state = Pdod(self.datadir + os.sep + 'state') # bot state

        # joined channels list .. used to join channels
        if not self.state.has_key('joinedchannels'):
            self.state['joinedchannels'] = []

        # allowed commands on the bot
        if not self.state.has_key('allowed'):
            self.state['allowed'] = []

        # channels we dont want ops in
        if not self.state.has_key('no-op'):
            self.state['no-op'] = []

        # channels we are op in
        if not self.state.has_key('opchan'):
            self.state['opchan'] = []

        # the time we joined a channel
        self.timejoined = {}

        # jabber type doesnt exists anymore .. set type to 'xmpp' instead
        self.type = self.type or 'gozernet'

        if self.type == 'jabber':
            self.type = 'xmpp'

        self.networkname = self.server
        self.jid = "%s@%s" % (self.nick, self.server)
        self.jids = {}
        self.shutloop = False
        self.cfg = cfg # the bots config
        self.orignick = "" # original nick
        self.blocking = 1 # use blocking sockets
        self.lastoutput = 0 # time of last output
        self.stopped = False # flag to set when bot is to be stopped
        self.connected = False # conencted flag
        self.connecting = False # connecting flag
        self.connectok = threading.Event() # event set when bot has connected
        self.waitingforconnect = False # flag to indicate we are waiting for connect
        self.starttime = time.time() # start time of the bot
        self.nrevents = 0 # number of events processed
        self.gcevents = 0 # number of garbage collected events
        self.less = Less(5) # output buffering
        self.userchannels = Dol() # list of channels a user is in
        self.channels = Channels(self.datadir + os.sep + 'channels') # channels
        self.userhosts = PersistState(self.datadir + os.sep + 'userhosts') # userhosts cache
        self.splitted = [] # list of splitted nicks
        self.throttle = [] # list of nicks that need to be throttled
        self.jabber = False # flag is set on jabber bots
        self.google = False
        if 'google' in self.host:
            self.google = True # flag is set on google bots
        try:
            import google.appengine.ext 
            self.google = True
        except:
            pass
        self.callbacks = callbacks
        self.monitor = Monitor()
        self.wait = Wait()
        self.privwait = Privwait()
        self.error = None
        
        # start runners
        runners_start()
        #self.monitor.start()
        
    def ownercheck(self, ievent, txt=None):

        """ 
            check whether an event originated from the bot owner. 

            :param ievent: event to check for owner with
            :param txt: optional txt to report to user when check fails
            :rtype: 1 or 0

            .. literalinclude:: ../../gozerbot/botbase.py
                :pyobject: BotBase.ownercheck

        """

        # use owner set in bot's config or else in global config
        owner = self.cfg['owner'] or config['owner']

        # check if event userhost in in owner .. check lists and string values
        if type(owner) == types.ListType:           
            if ievent.userhost in owner:            
                return 1
        elif owner == ievent.userhost:              
            return 1    
        else:
            rlog(100, self.name, 'failed owner check %s should be in %s' % (ievent.userhost, owner))
            if not txt:
                ievent.reply("only owner (see config file) is allowed to perform this command")
            else:
                ievent.reply("only owner (see config file) %s" % txt)
            return 0

    def save(self):

        """ save bot state. """

        self.channels.save()
        self.userhosts.save()
        self.state.save()

    def stop(self):

        """ stop the bot. """

        self.stopped = True
        rlog(10, self.name, 'stopped')

    def exit(self):

        """ shutdown the bot. overload this. """

        pass

    def connect(self, reconnect=True):

        """ connect the bot to the server. reconnects in the default case. """

        pass

    def say(self, printto, what, event=None, who=None, how='msg', fromm=None, speed=0, groupchat=False):
        print what

    def whois(self, nick):
        pass

    def sendraw(self, txt):
        print txt

    def voice(self, channel, txt):
        pass

    def action(self, channel, txt):
        pass

    def _raw(self, txt):
        print txt

    def settopic(self, channel, txt):
        pass

    def names(self, channel):
        pass

    def gettopic(self, channel):
        pass

    def _dcclisten(self, *args):
        pass

    def donick(self, nick, save=False, setorig=True):
        pass

    def fakein(self, txt):
        pass

    def part(self, channel):
        pass

    def serveforever(self):
        self.stopped = False
        self.shutloop = False

        while not self.stopped and not self.shutloop:
            try:
                import asyncore
                asyncore.poll(timeout=0.01)
            except ImportError:
                pass
            except Exception, ex:
                handle_exception()
                globalshutdown()
                os._exit(1)
            time.sleep(0.01)
            mainhandler.handle_one()

    def join(self, channel, password=""):
        pass

    def joinchannels(self):

        """ join all registered channels. overload this. """

        pass

    def connectwithjoin(self, reconnect=True):

        """ connect to the server and join channels. """

        self.connect(reconnect)
        self.connectok.wait()
        start_new_thread(self.joinchannels, ())

    def broadcast(self):

        """ announce a message to all channels. overload this"""

        pass

    def send(self, txt):

        """ send txt to the server. overload this"""

        pass

    def shutdown(self):

        """ close sockets of the bot. overload this"""

        pass

    def domsg(self, msg, response=False, wait=False):

        """ 
            excecute a message (txt line) on the bot.

            :param msg: text line to execute
            :type msg: string
            :rtype: None

            .. literalinclude:: ../../gozerbot/botbase.py
                :pyobject: BotBase.domsg
        """ 

        if response:
             msg.reply('executing %s (%s) on %s bot' % (msg.txt, msg.userhost, self.name))

        from gozerbot.plugins import plugins
        
        if wait:
            plugins.waitdispatch(self, msg)
        else:
            plugins.trydispatch(self, msg)

    def ratelimit(self, userhost, cpm=20):
        try:
            throttlestate['level'][userhost] = cpm 
            throttlestate.save()
            rlog(10, self.name, '%s throttled to %s cpm' % (userhost, cpm))
        except Exception, ex:
            rlog(100, self.name, "can't set throttle of %s" % userhost)
            handle_exception()

    def remoteout(self, request, event):
        request.wfile.write(event.tojson())

    def doevent(self, event):

        """ 
            dispatch an event. 

            :param event: event to dispatch.
            :rtype: None

        """

        if not event:
            return

        e = cpy(event)

        if event.isremote:
            rlog(10, self.name, 'remote event .. calling gn_callbacks')
            gn_callbacks.check(self, e)
        else:
            callbacks.check(self, e)

        if event.remotecmnd:
            plugins.trydispatch(self, event)
